package com.seckill.controller;

import com.google.common.util.concurrent.RateLimiter;
import com.seckill.common.error.BusinessException;
import com.seckill.common.error.ErrorEnum;
import com.seckill.common.model.CommonResult;
import com.seckill.common.service.RedisService;
import com.seckill.model.dto.UserDTO;
import com.seckill.mq.MQProducer;
import com.seckill.service.ItemService;
import com.seckill.service.OrderService;
import com.seckill.service.PromoService;
import com.seckill.util.CodeUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author lill
 * @date 2019-06-24
 */
@RestController()
@RequestMapping("/order")
@CrossOrigin
public class OrderController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private RedisService<UserDTO> userDTORedisService;

    @Autowired
    private RedisService<String> stringRedisService;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private MQProducer mqProducer;

    @Autowired
    private ItemService itemService;

    @Autowired
    private PromoService promoService;

    private ExecutorService executorService;

    private RateLimiter orderCreateRateLimiter;

    @PostConstruct
    public void init() {
        executorService = Executors.newFixedThreadPool(20);

        orderCreateRateLimiter = RateLimiter.create(300);

    }

    @RequestMapping(value = "/createOrder", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult createOrder(@RequestParam(name = "itemId") Integer itemId,
                                    @RequestParam(name = "promoId", required = false) Integer promoId,
                                    @RequestParam(name = "amount") Integer amount,
                                    @RequestParam(name = "promoToken", required = false) String promoToken) {
        if (orderCreateRateLimiter.acquire() <= 0) {
            throw new BusinessException(ErrorEnum.RATE_LIMIT);
        }

        // 获取登录信息
        String token = httpServletRequest.getParameterMap().get("token")[0];
        boolean isLogin = userDTORedisService.exists("user_" + token);
        if (!isLogin) {
            throw new BusinessException(ErrorEnum.USER_NOT_LOGIN, "用户还未登录，不能下单");
        }
        UserDTO userDTO = userDTORedisService.getCache("user_" + token);
        if (null == userDTO) {
            throw new BusinessException(ErrorEnum.USER_NOT_EXIST);
        }
        //校验秒杀令牌是否正确
        if (null != promoId) {
            String inRedisPromoToken = stringRedisService.getCache("promo_token_" + promoId + "_userid_" + userDTO.getId() + "_itemid_" + itemId);
            if (inRedisPromoToken == null) {
                throw new BusinessException(ErrorEnum.VALIDATION_REQUEST_PARAM_ERROR, "秒杀令牌校验失败");
            }
            if (!StringUtils.equals(promoToken, inRedisPromoToken)) {
                throw new BusinessException(ErrorEnum.VALIDATION_REQUEST_PARAM_ERROR, "秒杀令牌校验失败");
            }
        }
        //同步调用线程池的submit方法
        //拥塞窗口为20的等待队列，用来队列化泄洪
        Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() {
                //加入库存流水init状态
                // 加入库存流水init状态
                String stockLogId = itemService.initStockLog(itemId, amount);
                // 完成事务消息
                if (!mqProducer.transactionAsyncReduceStock(userDTO.getId(), itemId, promoId, amount, stockLogId)) {
                    throw new BusinessException(ErrorEnum.TRANSACTION_FAIL, "下单失败");
                }
                return null;
            }
        });

        try {
            future.get();
        } catch (InterruptedException e) {
            throw new BusinessException(ErrorEnum.SYS_ERROR);
        } catch (ExecutionException e) {
            throw new BusinessException(ErrorEnum.SYS_ERROR);
        }

        return CommonResult.success();

    }

    @RequestMapping(value = "/generateToken", method = {RequestMethod.POST})
    @ResponseBody
    public CommonResult generatetoken(@RequestParam(name = "itemId") Integer itemId,
                                      @RequestParam(name = "promoId") Integer promoId) throws BusinessException {
        //根据token获取用户信息
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(ErrorEnum.USER_NOT_LOGIN);
        }
        //获取用户的登陆信息
        UserDTO userDTO = userDTORedisService.getCache("user_" + token);
        if (null == userDTO) {
            throw new BusinessException(ErrorEnum.USER_NOT_LOGIN);
        }
        //获取秒杀访问令牌
        String promoToken = promoService.generateSecondKillToken(promoId, itemId, userDTO.getId());

        if (null == promoToken) {
            throw new BusinessException(ErrorEnum.VALIDATION_REQUEST_PARAM_ERROR, "生成令牌失败");
        }
        System.out.println("promoToken: " + promoToken);
        //返回对应的结果
        return CommonResult.success(promoToken);
    }


    @RequestMapping(value = "/generateVerifyCode", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void generateVerifyCode(HttpServletResponse response) throws IOException {
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(ErrorEnum.USER_NOT_LOGIN, "用户还未登陆，不能生成验证码");
        }
        UserDTO userDTO = (UserDTO) userDTORedisService.getCache("user_" + token);
        if (null == userDTO) {
            throw new BusinessException(ErrorEnum.USER_NOT_LOGIN, "用户还未登陆，不能生成验证码");
        }
        Map<String, Object> map = CodeUtil.generateCodeAndPic();
        stringRedisService.setCache("verify_code_" + userDTO.getId(), map.get("code").toString(), 10, TimeUnit.MINUTES);
        ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", response.getOutputStream());
    }
}
